from django.core.paginator import Paginator, InvalidPage, EmptyPage
from django.http import QueryDict
from django.utils.text import capfirst

from itertools import groupby

import django_filters

class Group(object):
	fields = []
	
	def add_field(self, field_name, value):
		self.fields.append(field_name)
		setattr(self, field_name, value)
	
	def __eq__(this, that):
		return reduce(lambda x, y: x and getattr(this, y) == (getattr(that, y)), this.fields, True)

class Item(Group):
	fields = []
	
class InfoSetField(object):
	
	def __init__(self, order_kw, order_field_kw, info_set, field_name, nice_name, current_ordered_field, current_ordered_dir):
		self.order_kw = order_kw
		self.order_field_kw = order_field_kw
		
		self.info_set = info_set
		self.field = field_name
		self.label = nice_name
		self.is_sorted = field_name == current_ordered_field
		
		if self.is_sorted:
			self.sorted_ascending = current_ordered_dir == '+'
			self.sorted_descending = not self.sorted_ascending
		else:
			self.sorted_ascending = False
			self.sorted_descending = False
		
		if self.is_sorted:
			if current_ordered_dir == '+':
				# now desc
				self.toggle_order_link = self._get_desc_link()
			else:
				# now no sort
				self.toggle_order_link = self._get_no_sort_link()
		else:
			self.toggle_order_link = self._get_asc_link()
			
		self.asc_order_link = self._get_asc_link()
		self.desc_order_link = self._get_desc_link()
		
	def _get_desc_link(self):
		kw = {self.order_kw: '-', self.order_field_kw: self.field}
		return self.info_set.get_params(**kw)
	
	def _get_asc_link(self):
		kw = {self.order_kw: '+', self.order_field_kw: self.field}
		return self.info_set.get_params(**kw)
	
	def _get_no_sort_link(self):
		kw = {self.order_kw: None, self.order_field_kw: None}
		return self.info_set.get_params(**kw)
		

class InfoSet(object):
	PAGE_KEY = 'page'
	
	def __init__(self, request = QueryDict('')):
		# TODO loading meta from parent classes?
		self._meta = self.Meta
		
		self._request = request
		
		self.group_field_names = self._get_field_names_from_list(self._meta.group_fields)
		self.item_field_names = self._get_field_names_from_list(self._meta.item_fields)
		
		if not hasattr(self._meta, 'group_ordering'):
			self._meta.group_ordering = self.group_field_names
		
		if not hasattr(self._meta, 'item_ordering'):
			self._meta.item_ordering = self.item_field_names
			
		if not hasattr(self._meta, 'items_per_page'):
			self._meta.items_per_page = 50
			
		if not hasattr(self._meta, 'filter_set_class'):
			# create a filter set class
			field_names = []
			field_names.extend(self.group_field_names)
			field_names.extend(self.item_field_names)
			inner_meta = type('Meta', (object,), dict(model = self._meta.model, fields = field_names))
			
			filter_set_class = type('%sFilterSet' % self.__class__.__name__, (django_filters.FilterSet,), dict(Meta = inner_meta))
			self._meta.filter_set_class = filter_set_class
			
	def _get_field_names_from_list(self, field_list):
		res = []
		for field in field_list:
			if isinstance(field, (list, tuple)):
				res.append(field[0])
			else:
				res.append(field)
		return res
	
	def _page(self):
		if not self.loaded:
			self._process()
		return self.__page
	page = property(_page)
	
	def _items_on_page(self):
		return len(self.page.object_list)
	items_on_page = property(_items_on_page)
	
	def _groups(self):
		if not self.loaded:
			self._process()
		return self.__groups
	groups = property(_groups)
	
	def _next_page_params(self):
		return self.get_params(page_number = self.page.next_page_number())			
	next_page_params = property(_next_page_params)
	
	def _previous_page_params(self):
		return self.get_params(page_number = self.page.previous_page_number())			
	previous_page_params = property(_previous_page_params)
	
	def _first_page_params(self):
		return self.get_params(page_number = 1)			
	first_page_params = property(_first_page_params)
	
	def _last_page_params(self):
		return self.get_params(page_number = self.page.paginator.num_pages)			
	last_page_params = property(_last_page_params)
	
	def _get_group_fields(self):
		if not self.loaded:
			self._process()
		return self.__group_fields
	group_fields = property(_get_group_fields)
	
	def _get_item_fields(self):
		if not self.loaded:
			self._process()
		return self.__item_fields
	item_fields = property(_get_item_fields)
	
	def _get_filter_set(self):
		if not self.loaded:
			self._process()
		return self.__filter_set
	filter_set = property(_get_filter_set)
	
	loaded = False 
	
	def _process(self):
		# performs the grouping
		items = self._meta.queryset
		
		# filter
		filter_set = self._meta.filter_set_class(data = self._request, queryset = items, prefix = 'filter')
		items = filter_set.qs
		self.__filter_set = filter_set
		
		# sort
		# update orderings based on request
		group_order_dir = self._request.get('o_gt', None)
		group_order = '+' if group_order_dir == 'asc' else '-' if group_order_dir == 'desc' else None
		group_order_prefix = '-' if group_order == '-' else ''
		group_order_field_idx = int(self._request.get('o_gi', '-1'))
		if 0 <= group_order_field_idx < len(self.group_field_names):
			group_order_field = self.group_field_names[group_order_field_idx]
		else:
			group_order_field = None
		
		item_order_dir = self._request.get('o_it', None)
		item_order = '+' if item_order_dir == 'asc' else '-' if item_order_dir == 'desc' else None
		item_order_prefix = '-' if item_order == '-' else ''
		item_order_field_idx = int(self._request.get('o_ii', '-1'))
		if 0 <= item_order_field_idx < len(self.item_field_names):
			item_order_field = self.item_field_names[item_order_field_idx]
		else:
			item_order_field = None
		
		ordering = []
		if group_order:
			ordering.append("%s%s" % (group_order_prefix, group_order_field))
			ordering.extend(filter(lambda x: not (x.startswith(group_order_field) or x.startswith('-' + group_order_field)), self._meta.group_ordering))
		else:
			ordering.extend(self._meta.group_ordering)
		
		if item_order:
			ordering.append("%s%s" % (item_order_prefix, item_order_field))
			ordering.extend(filter(lambda x: not (x.startswith(item_order_field) or x.startswith('-' + item_order_field)), self._meta.item_ordering))
		else:
			ordering.extend(self._meta.item_ordering)
				
		items = items.order_by(*ordering)
		
		self.__group_order = group_order
		self.__group_order_field = group_order_field
		self.__item_order = item_order
		self.__item_order_field = item_order_field
		
		# page
		paginator = Paginator(items, self._meta.items_per_page)
		try:
			page = int(self._request.get(self.PAGE_KEY, '1'))
		except ValueError:
			page = 1
		
		try:
			self.__page = paginator.page(page)
		except (EmptyPage, InvalidPage):
			self.__page = paginator.page(paginator.num_pages)
		
		items = self.__page.object_list
		
		# group
		groups = []
		grouped_up = groupby(items, lambda x: self._make_key(x)) 
		
		for key, group in grouped_up:
			key.items = self._make_items(group)
			groups.append(key)
		
		self.__groups = groups
		
		self.loaded = True
		
		self.__group_fields = self._build_field_list('group_order', 'group_order_field', self._meta.group_fields, self.__group_order, self.__group_order_field)
		self.__item_fields = self._build_field_list('item_order', 'item_order_field', self._meta.item_fields, self.__item_order, self.__item_order_field)		
		
	def _build_field_list(self, order_kw, order_field_kw, field_list, order_dir, order_field):
		"""
		Build a list of field records
		"""
		res = []
		for field in field_list:
			field_item = InfoSetField(order_kw, order_field_kw, self, field, capfirst(field), order_field, order_dir)
			res.append(field_item)
		return res
		
	def get_params(self, include_filter = True, **kwargs):
		"""
		Builds query parameters for a GET
		"""
		
		if 'page_number' in kwargs:
			page_number = kwargs['page_number']
		else:
			page_number = self.page.number
			
		if 'group_order' in kwargs:
			group_order = kwargs['group_order']
		else:
			group_order = self.__group_order
		
		if 'group_order_field' in kwargs:
			group_order_field = kwargs['group_order_field']
		else:
			group_order_field = self.__group_order_field
		
		if 'item_order' in kwargs:
			item_order = kwargs['item_order']
		else:
			item_order = self.__item_order
		
		if 'item_order_field' in kwargs:
			item_order_field = kwargs['item_order_field']
		else:
			item_order_field = self.__item_order_field
				
		params = "%s=%s" % (self.PAGE_KEY, page_number)
		if group_order:
			params += "&o_gt=%s&o_gi=%s" % (('asc' if group_order == '+' else 'desc' if group_order == '-' else 'x'), (self.group_field_names.index(group_order_field)))
		if item_order:
			params += "&o_it=%s&o_ii=%s" % (('asc' if item_order == '+' else 'desc' if item_order == '-' else 'x'), (self.item_field_names.index(item_order_field)))
		
		q = QueryDict(params, mutable = True)
		if include_filter:
			filter_keys = [i for i in self._request.iterkeys() if i.startswith('filter')]
			for filter_key in filter_keys:
				q.appendlist(filter_key, self._request[filter_key])
		
		return q
	
	def _make_items(self, item_list):
		"""
		Makes a list of items
		"""
		res = []
		for item in item_list:
			r = Item()
			for item_field in self.item_field_names:
				r.add_field(item_field, getattr(item, item_field))
			r.add_field('item', item)
			res.append(r)
		return res
	
	def _make_key(self, x):
		"""
		Makes the grouping key for some given item instance
		"""
		r = Group()
		for group_field in self.group_field_names:
			r.add_field(group_field, getattr(x, group_field))
		# r.add_field('_first_item', x)
		return r
	
